#include "igslib_common_use.h"
#include "igslib_iutil.h"
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/sysinfo.h>
#include "md5.h"
#include "aes.h"
#include <chrono>
#include <thread>

namespace igslib
{
    namespace util
    {
        int GetFuseState()
        {
            int rtn = 0;
            char rx;
            const char* fuse_node = "/dev/eFuse";
            int fd = open(fuse_node, O_RDONLY);
            if (fd < 0)
            {
                std::stringstream ss;
                ss << "Can't open '" << fuse_node << "'";
                util::Log(ss.str().c_str());
                rtn = -1;
                return rtn;
            }

            int ret = read(fd, &rx, 1);
            if (ret<0)
            {
                rtn = -2;
                util::Log("Can't get fuse infomation.");
                return rtn;
            }
            close(fd);
            rtn = static_cast<int>(rx);
            return rtn;
        }
        std::string GetUUID()
        {
            std::string rtn("ERROR");
            char rx[6];
            const char* uuid_node = "/dev/MAC_addr";
            int fd = open(uuid_node, O_RDONLY);
            if (fd < 0)
            {
                std::stringstream ss;
                ss << "Can't open '" << uuid_node << "'";
                util::Log(ss.str().c_str());
                return rtn;
            }
            int ret = read(fd, &rx, 6);
            if (ret<0)
            {
                util::Log("Can't get uuid infomation.");
                return rtn;
            }
            char p[20];
            int i;
            for (i=0; i<6; i++)
            {
                sprintf(&p[i*3], "%02x:", rx[i] );
            }
            p[i*3-1] = '\0';
            close(fd);
            rtn.assign(p);
            return rtn;
        }
        void SleepMSTime(int value)
        {
            std::chrono::milliseconds duration(value);
            std::this_thread::sleep_for(duration);
        }
        unsigned long long GetAvailableMemoryBytes()
        {
            unsigned long long rtn = 0;
            //int fd = open("/proc/meminfo",O_RDONLY);
            //if (fd >= 0)
            //{
            //    char buf[1024];
            //    size_t read_length = read(fd,buf,sizeof(buf));
            //    if (read_length)
            //    {
            //        std::string info(buf);
            //        std::match_results mr;
            //        std::regex_search r(info,mr,std::regex("MemFree:[ \t]+"));
            //    }
            //    close(fd);
            //}
            return rtn;
        }
        size_t GetTicks()
        {
            long ticks;
            struct timeval now;
            gettimeofday(&now, NULL);
            ticks = now.tv_sec * 1000l;
            ticks += now.tv_usec / 1000l;
            return static_cast<size_t>(ticks);
        }
        int KbHit()
        {
            int rtn = 0;
            struct termios oldt, newt;  
            int ch;  
            int oldf;  
            int op_result;
            op_result = tcgetattr(STDIN_FILENO, &oldt);  
            if (op_result == -1)
            {
                util::Log("Can't get terminal attributes.");
                return -1;
            }
            newt = oldt;  
            newt.c_lflag &= ~(ICANON | ECHO);  
            op_result = tcsetattr(STDIN_FILENO, TCSANOW, &newt);  
            if (op_result == -1)
            {
                util::Log("Can't set terminal attributes.");
                return -2;
            }
            oldf = fcntl(STDIN_FILENO, F_GETFL, 0);  
            if (oldf == -1)
            {
                util::Log("File control failed.");
                return -3;
            }
            op_result = fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);  
            if (op_result == -1)
            {
                util::Log("File control failed.");
                return -3;
            }
            ch = getchar();  
            op_result = tcsetattr(STDIN_FILENO, TCSANOW, &oldt);  
            if (op_result == -1)
            {
                util::Log("Can't set terminal attributes.");
                return -2;
            }
            op_result = fcntl(STDIN_FILENO, F_SETFL, oldf);  
            if (op_result == -1)
            {
                util::Log("File control failed.");
                return -3;
            }
            if(ch != EOF)  
            {  
                ungetc(ch, stdin);  
                rtn = getchar();
            }  
            return rtn;
        }
        float GetFPS()
        {
            static float rtn = 0;
            static unsigned long long d0 = GetTicks();
            static unsigned long long d1 = GetTicks();
            static float frames = 0;

            ++frames;
            d1 = GetTicks();
            unsigned long long delta = d1-d0;
            if (delta >= 1000)
            {
                rtn = frames;
                d0 = GetTicks();
                frames = 0;
            }
            return rtn;
        }
        void Log(const char* format, ...)
        {
            va_list args;
            va_start(args, format);
            __android_log_vprint(ANDROID_LOG_DEBUG, "<igslib log>: ",  format, args);
            vprintf(format,args);
            va_end(args);
        }
        std::string ReadDeviceNodeInfo(const std::string& node)
        {
            std::string rtn;
            std::stringstream ss;
            ss << __func__;
            int fd = open(node.c_str(),O_RDONLY);
            if (fd >= 0)
            {
                char buf[1024];
                int read_length = read(fd,buf,sizeof(buf));
                if (read_length >= 0)
                {
                    rtn.assign(buf,read_length);
                }
                else
                {
                    ss << "/read() fail>";
                    goto throw_exception;
                }
                close(fd);
            }
            else
            {
                ss << "/open() fail>";
                goto throw_exception;
            }
            return rtn;

throw_exception:
            ss << "node=" << node;
            util::Log(ss.str().c_str());
            rtn = "ERROR";
            return rtn;
        }
        int WriteDeviceNodeInfo(const std::string& node,const std::string& value)
        {
            int rtn = 0;
            std::stringstream ss;
            ss << __func__;
            int fd = open(node.c_str(),O_WRONLY);
            if (fd >= 0)
            {
                int rtn = write(fd,value.c_str(),value.size());
                if (rtn < 0)
                {
                    ss << "/write() fail>";
                    goto throw_exception;
                }
                close(fd);
            }
            else
            {
                ss << "/open() fail>";
                goto throw_exception;
            }
            return rtn;

throw_exception:
            ss << "node=" << node;
            util::Log(ss.str().c_str());
            rtn = -1;
            return rtn;
        }
        std::vector<unsigned char> GetDigest(const std::vector<unsigned char>& input)
        {
            static MD5_CTX md5;
            static bool init = false;
            std::vector<unsigned char> rtn(16);
            
            if (init == false)
            {
                MD5Init(&md5);
                init = true;
            }
            MD5Update(&md5,const_cast<unsigned char*>(input.data()),input.size());
            MD5Final(rtn.data(),&md5);
            return rtn;
        }
        std::vector<unsigned char> symmetric_crypto(bool is_encrypt,const std::vector<unsigned char>& key,const std::vector<unsigned char>& input)
        {
            std::vector<unsigned char> rtn(input.size());
            auto bits = key.size()*8;
            
            ENUM_KEYSIZE ks = BIT128;
            if (bits >= 256)
            {
                ks = BIT256;
            }
            else if (bits >= 192)
            {
                ks = BIT192;
            }

            vAes_SetKey(ks,const_cast<unsigned char*>(key.data()));

            if (is_encrypt)
            {
                aes_ecb_encrypt(
                        const_cast<unsigned char*>(input.data()),
                        const_cast<unsigned char*>(rtn.data()),
                        input.size()
                        );
            }
            else
            {
                aes_ecb_decrypt(
                        const_cast<unsigned char*>(input.data()),
                        const_cast<unsigned char*>(rtn.data()),
                        input.size()
                        );
            }
            return rtn;
        }
        std::vector<unsigned char> SymmetricEncrypt(const std::vector<unsigned char>& key,const std::vector<unsigned char>& input)
        {
            return symmetric_crypto(true,key,input);
        }
        std::vector<unsigned char> SymmetricDecrypt(const std::vector<unsigned char>& key,const std::vector<unsigned char>& input)
        {
            return symmetric_crypto(false,key,input);
            return symmetric_crypto(true,key,input);
        }
        double GetElapsedMilliseconds()
        {
            double rtn = 0;
            using namespace std::chrono;
            time_point<system_clock> init(milliseconds(0));
            static time_point<system_clock> begin(milliseconds(0));
            time_point<system_clock> end = system_clock::now();

            if (begin != init)
            {
                duration<double> et = end-begin;
                rtn = et.count();
            }
            begin = end;

            return rtn;
        }
        std::string GetRevision()
        {
            std::string rtn(RCS_REVISION);
            return rtn;
        }

    };
};
